Skip to content

fix(integrity): dedupe resolved packageIds at the source (#506)#509

Merged
Kamirus merged 2 commits intomainfrom
fix/integrity-dedupe-package-ids
Apr 22, 2026
Merged

fix(integrity): dedupe resolved packageIds at the source (#506)#509
Kamirus merged 2 commits intomainfrom
fix/integrity-dedupe-package-ids

Conversation

@Kamirus
Copy link
Copy Markdown
Collaborator

@Kamirus Kamirus commented Apr 22, 2026

Fixes #506. Alternative to #507.

What changes for users

mops install (and any flow that runs --lock check) no longer fails with Mismatched number of resolved packages: N vs M on projects whose resolved deps include multiple aliases (e.g. base, base@0, base@0.16) pinned to the same name@version. Previously the only workaround was --lock ignore.

Root cause

getResolvedMopsPackageIds returned a list with duplicates: resolvePackages() keeps each alias as its own entry, then getPackageId collapses the alias suffix via getDepName, so two aliases pinning the same name@version produced the same id twice. mops.lock's hashes is keyed by packageId and naturally dedup'd, so the count comparison in checkLockFile mismatched.

Why this approach over #507

#507 dedupes only at the count comparison. This PR dedupes inside getResolvedMopsPackageIds, which (a) makes the function's contract match its name, and (b) stops sending duplicate ids to the canister's getFileHashesByPackageIds. Downstream membership checks (includes, in) were already dedupe-safe, so no other call site needed changes.

Test plan

Added a regression fixture pinning core and core@1 to 1.0.0 plus a test asserting mops install exits clean and stderr doesn't contain the mismatch error. Verified that reverting the fix makes the Jest test fail with the expected error string.

When multiple mops.toml dependency aliases (e.g. `base`, `base@0`,
`base@0.16`) pin to the same `name@version`, `getResolvedMopsPackageIds`
returned a list with duplicates, while `mops.lock`'s `hashes` map is
naturally keyed by packageId. The integrity count check then failed with
"Mismatched number of resolved packages" on otherwise-valid projects.

Dedupe inside `getResolvedMopsPackageIds` so callers (registry queries
and lockfile checks) see one entry per packageId. This also avoids
sending duplicate ids to `getFileHashesByPackageIds` on the canister.

Add a regression test fixture pinning `core` and `core@1` to the same
version; without the fix `mops install` exits 1 with the mismatch error.

Fixes #506.

Made-with: Cursor
@Kamirus Kamirus requested a review from a team as a code owner April 22, 2026 08:17
Made-with: Cursor
@Kamirus Kamirus enabled auto-merge (squash) April 22, 2026 08:20
@Kamirus Kamirus merged commit 78825ba into main Apr 22, 2026
27 checks passed
@Kamirus Kamirus deleted the fix/integrity-dedupe-package-ids branch April 22, 2026 08:25
Kamirus added a commit that referenced this pull request Apr 23, 2026
…515)

Fixes #514.

## What changes for users

`mops install --lock update` now reliably regenerates `mops.lock` even
when the existing lockfile has a stale or corrupt per-file hash.
Previously this case silently no-op'd and then exited 1 on the follow-up
integrity check, so `--lock update` could not recover a broken lock —
the only escape hatch was `rm mops.lock`. That hatch is no longer
required.

Default `mops install` (no flag) is unchanged; the fast-path shortcut
still fires when the lockfile's `mopsTomlDepsHash` matches.

## Root cause

`updateLockFile` short-circuits via `checkLockFileLight`, which only
validates `mopsTomlDepsHash`. When mops.toml hasn't changed but the
lockfile's per-file `hashes` block is corrupt, the shortcut fires →
nothing is rewritten → the subsequent `checkLockFile(force)` finds the
same corrupt hash and calls `process.exit(1)`. Users get "Integrity
check failed" with no way out except manual deletion of `mops.lock`.

## Fix

Thread the existing `force` boolean from `checkIntegrity` into
`updateLockFile`. `force = !!lock` is already true exactly when the user
explicitly passed `--lock update`, so:

- `mops install` (no flag, outside CI) → `force = false`, keeps the
fast-path
- `mops install --lock update` → `force = true`, bypasses the
light-check shortcut, always regenerates
- `mops install --lock check` / `--lock ignore` → unchanged

## Test plan

Added a regression test (`--lock update rewrites a lockfile with a
corrupt file hash`) that:
1. Runs `mops install` to produce a valid lockfile.
2. Surgically replaces one per-file hash with a bogus value.
3. Runs `mops install --lock update`.
4. Asserts exit 0 and that the bogus hash is no longer in the lockfile.

Verified reverting the fix (restoring `updateLockFile()` / `if
(checkLockFileLight()) return`) makes the new Jest test fail with the
expected "Integrity check failed" / "Mismatched hash" error.

## Related

- #509 (merged as part of 2.12.2) fixed the alias-tuple count mismatch
that was the common *source* of corrupt lockfiles in the wild. This PR
makes `--lock update` able to heal locks that were already written in
the corrupt state.

---------

Co-authored-by: Kamil Listopad <listopadkamil@gmail.com>
Co-authored-by: Kamil Listopad <kamil.listopad@caffeine.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrity check miscounts alias-tuples as distinct packages ("Mismatched number of resolved packages N vs M")

1 participant